/******************************************************************************
* 文件名称: port.c
* 创建者の: 孔佳伟
* 个人主页: https://gitee.com/thin-wind/jump
* 修改记录: 
******************************************************************************/

#include "port.h"
#include "task.h"
#include "rtosConfig.h"


#define portINITIAL_XPSR        (0x01000000)
#define portSTART_ADDRESS_MASK  ((StackType_t)0xfffffffeUL)


/*
* 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.7，百度搜索“PM0056”即可找到这个文档
* 在 Cortex-M 中，内核外设 SCB 中 SHPR3 寄存器用于设置 SysTick 和 PendSV 的异常优先级
* System handler priority register 3 (SCB_SHPR3) SCB_SHPR3：0xE000 ED20
* Bits 31:24 PRI_15[7:0]: Priority of system handler 15, SysTick exception
* Bits 23:16 PRI_14[7:0]: Priority of system handler 14, PendSV
*/
#define portNVIC_SYSPRI2_REG                (*(( volatile uint32_t *)0xe000ed20))

#define portNVIC_PENDSV_PRI                 (((uint32_t) 14 ) << 16UL)
#define portNVIC_SYSTICK_PRI                (((uint32_t) 15 ) << 24UL)

/* SysTick 配置寄存器 */
#define portNVIC_SYSTICK_CTRL_REG			(*((volatile uint32_t *)0xe000e010))
#define portNVIC_SYSTICK_LOAD_REG			(*((volatile uint32_t *)0xe000e014))

#ifndef configSYSTICK_CLOCK_HZ
	#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
	/* 确保SysTick的时钟与内核时钟一致 */
	#define portNVIC_SYSTICK_CLK_BIT	    (1UL << 2UL)
#else
	#define portNVIC_SYSTICK_CLK_BIT	    (0)
#endif

#define portNVIC_SYSTICK_INT_BIT			(1UL << 1UL)
#define portNVIC_SYSTICK_ENABLE_BIT			(1UL << 0UL)



// 示临界段嵌套计数器
// 在 FreeRTOS 中定义时默认为 0xaaaaaaaaUL，在 xPortStartScheduler 启动调度时赋值为 0
// 这里我们直接赋值 0，省点事，因为我们也没有啥异常检测机制
static UBaseType_t uxCriticalNesting = 0;


static void prvTaskExitError(void)
{
    while(1);
}

// 初始化栈-异常发生时出栈顺序与初始化时入栈顺序正好相反
StackType_t* pxPortInitialiseStack(StackType_t* pxTopOfStack, TaskFunction_t pxCode, void* pvParameters )
{
    // 异常发生时，CPU 自动从栈中加载到 CPU 寄存器的内容
    // 包括 8 个寄存器，分别为 R0、R1、R2、R3、R12、R14、R15 和 xPSR 的位 24，且顺序不能变
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_XPSR;
    pxTopOfStack--;
    *pxTopOfStack = ((StackType_t)pxCode) & portSTART_ADDRESS_MASK;
    pxTopOfStack--;
    *pxTopOfStack = (StackType_t)prvTaskExitError; // 任务返回地址，正常情况下任务是不会返回的，如果返回，则跳转到一个死循环中
    pxTopOfStack -= 5; // R12, R3, R2 and R1 默认初始化为 0
    
    *pxTopOfStack = ( StackType_t ) pvParameters;

    // 异常发生时，手动加载到 CPU 寄存器的内容 - R11, R10, R9, R8, R7, R6, R5, R4
    pxTopOfStack -= 8;

    // 返回栈顶指针，此时 pxTopOfStack 指向空闲栈
    return pxTopOfStack;
}

 /*
  * 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3，百度搜索“PM0056”即可找到这个文档
  * 在 Cortex-M 中，内核外设SCB 的地址范围为：0xE000ED00-0xE000ED3F
  * 0xE000ED008 为 SCB 外设中 SCB_VTOR 这个寄存器的地址，里面存放的是向量表的起始地址，即 MSP 的地址
  */
//  __asm void prvStartFirstTask( void )
// {
//     PRESERVE8

//     /* 在 Cortex-M 中，0xE000ED08 是 SCB_VTOR 这个寄存器的地址，
//     里面存放的是向量表的起始地址，即 MSP 的地址 */
//     ldr r0, =0xE000ED08
//     ldr r0, [r0]
//     ldr r0, [r0]

//     /* 设置主堆栈指针 msp 的值 */
//     msr msp, r0

//     /* 使能全局中断 */
//     cpsie i
//     cpsie f
//     dsb
//     isb

//     /* 调用 SVC 去启动第一个任务 */
//     svc 0
//     nop
//     nop
//  }
// 启动第一个任务
void prvStartFirstTask( void )
{
    // __asm("PRESERVE8");
    __asm("ALIGN=8");
    /* 在 Cortex-M 中，0xE000ED08 是 SCB_VTOR 这个寄存器的地址，
    里面存放的是向量表的起始地址，即 MSP 的地址 */
    __asm("ldr r0, =0xE000ED08");   // (1) 将 0xE000ED08 这个立即数加载到寄存器 R0
    __asm("ldr r0, [r0]");          // (2) 将 0xE000ED08 这个地址指向的内容加载到寄存器 R0，此时 R0
                                    //     等于 SCB_VTOR 寄存器的值，等于 0x00000000，即 memory 的起始地址
    __asm("ldr r0, [r0]");          // (3) 将 0x00000000 这个地址指向的内容加载到 R0，此时 R0 = 0x200008DB

    // 设置主堆栈指针 msp 的值
    __asm("msr msp, r0");           // (4) 将 R0 的值存储到 MSP，此时 MSP = 0x200008DB
                                    //     这是主堆栈的栈顶指针，其实这一操作有点多余，
                                    //     因为当系统启动的时候，执行完 Reset_Handler 的时候，向量表已经初始
                                    //     化完毕，MSP 的值就已经更新为向量表的起始值，即指向主堆栈的栈顶指针

    // 使能全局中断
    __asm("cpsie i");
    __asm("cpsie f");
    __asm("dsb");                   //     数据同步屏障，确保之前存储器访问已经完成
    __asm("isb");                   //     指令同步屏障，确保之前指令都已经执行完毕

    // 调用 SVC 去启动第一个任务
    __asm("svc 0");                 // (5) 触发软中断，接下来将会执行 SVC 对应的中断服务程序
    __asm("nop");
    __asm("nop");
}


// 开启调度
BaseType_t xPortStartScheduler(void)
{
    // 配置 PendSV 和 SysTick 的中断优先级为最低
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    vPortSetupTimerInterrupt();

    // 启动第一个任务，不再返回
    prvStartFirstTask();

    // 不应该运行到这里
    return 0;
}

// 开始真正启动第一个任务，不再返回
// SVC 中断只在启动第一次任务时会调用一次，以后均不调用
// __asm void vPortSVCHandler( void )
// {
//     extern pxCurrentTCB;

//     PRESERVE8

//     ldr r3, =pxCurrentTCB
//     ldr r1, [r3]
//     ldr r0, [r1]
//     ldmia r0!, {r4-r11}
//     msr psp, r0
//     isb
//     mov r0, #0
//     msr basepri, r0
//     orr r14, #0xd

//     bx r14
//  }
void vPortSVCHandler(void)
{
    // 声明外部变量 pxCurrentTCB，pxCurrentTCB 是一个在 task.c 中定义的全局指针，
    // 用于指向当前正在运行或者即将要运行的任务的任务控制块
    extern TCB_t* pxCurrentTCB;

    // __asm("PRESERVE8");
    __asm("ALIGN=8");
    __asm("ldr r3, =pxCurrentTCB");     // (1) 加载 pxCurrentTCB 的地址到 r3
    __asm("ldr r1, [r3]");              // (2) 加载 r3 指向地址内的值到 r1
    __asm("ldr r0, [r1]");              // (3) 加载 r1 指向地址内的值到 r0，以上操作就是将 pxCurrentTCB 指向的数据起始地址赋值给 r0，
                                        //     由于 pxCurrentTCB 指向的任务块 tskTCB 的第一个元素就是栈顶 pxTopOfStack
                                        //     所以此时 r0 指向任务的栈顶 pxTopOfStack
    __asm("ldmia r0!, {r4-r11}");       // (4) 以 r0 为基地址，将栈中向上增长的 8 个字的内容加载到 CPU 寄存器 r4~r11，同时 r0 也会跟着自增
    __asm("msr psp, r0");               // (5) 将新的栈顶指针 r0 更新到 psp，任务执行的时候使用的堆栈指针是 psp
    __asm("isb");                       //     指令同步屏障，清楚流水线并且确保之前的指令全都执行完毕
    __asm("mov r0, #0");                //     r0 = 0
    __asm("msr basepri, r0");           //     设置 basepri 寄存器的值为 0，即打开所有中断
                                        //     basepri 是一个中断屏蔽寄存器，大于等于此寄存器值的中断都将被屏蔽
    __asm("orr r14, #0xd");             //     当从 SVC 中断服务退出前，通过向 r14 寄存器最后 4 位按位或上 0x0D，
                                        //     使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作并返回后进入任务模式、返回 Thumb 状态
                                        //     在 SVC 中断服务里面，使用的是 MSP 堆栈指针，是处在 ARM 状态
    
    __asm("bx r14");                    // (6) 异常返回，这个时候出栈使用的是 psp 指针，自动将栈中的剩下内容加载到 CPU 寄存器
}


// PendSV 中断服务函数-实现任务切换
// __asm void xPortPendSVHandler(void)
// {
//     extern pxCurrentTCB;
//     extern vTaskSwitchContext;

//     PRESERVE8

//     mrs r0, psp
//     isb

//     ldr r3, =pxCurrentTCB
//     ldr r2, [r3]

//     stmdb r0!, {r4-r11}
//     str r0, [r2]

//     stmdb sp!, {r3, r14}
//     mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
//     msr basepri, r0
//     dsb
//     isb
//     bl vTaskSwitchContext
//     mov r0, #0
//     msr basepri, r0 
//     ldmia sp!, {r3, r14}

//     ldr r1, [r3]
//     ldr r0, [r1]
//     ldmia r0!, {r4-r11}
//     msr psp, r0
//     isb
//     bx r14
//     nop
// }
void xPortPendSVHandler(void)
{
    // 声明外部变量 pxCurrentTCB，pxCurrentTCB 是一个在 task.c 中定义的全局指针，
    // 用于指向当前正在运行或者即将要运行的任务的任务控制块
    extern TCB_t* pxCurrentTCB;

    // 声明外部函数 vTaskSwitchContext，功能是任务切换，本质是更新 pxCurrentTCB
    extern void vTaskSwitchContext(void);

    // PRESERVE8
    __asm("ALIGN=8");                   //     8 字节对齐

                                        //     当进入 PendSVC Handler 时，上一个任务运行的环境
                                        //     即：xPSR，PC（任务入口地址），R14，R12，R3，R2，R1，R0（任务的形参）
                                        //     这些 CPU 寄存器的值会自动存储到任务的栈中
    __asm("mrs r0, psp");               // (1) 将 PSP 的值存储到 r0
    __asm("isb");                       //     指令同步屏障，确保之前指令都已经执行完毕
    __asm("ldr r3, =pxCurrentTCB");     //     加载 pxCurrentTCB 的地址到 r3
    __asm("ldr r2, [r3]");              //     加载 r3 指向的内容到 r2，即 r2 等于 pxCurrentTCB
    __asm("stmdb r0!, {r4-r11}");       // (2) 以 r0 作为基址（(1) 处 r0 = psp），将 CPU 寄存器 r4~r11 的值存储到任务栈，同时更新 r0 的值
                                        //     将 CPU 寄存器 r4~r11 的值存储到任务栈，同时更新 r0 的值
    __asm("str r0, [r2]");              // (3) 将 r0 的值存储到 r2 指向的内容，而 r2 指向 pxCurrentTCB
                                        //     即为将 r0 的值存储到上一个任务的栈顶指针 pxTopOfStack
                                        //     至此，上下文的保存工作完成

    __asm("stmdb sp!, {r3, r14}");      // (4) 将 R3 和 R14 临时压入堆栈
                                        //     在整个系统中，中断使用的是主堆栈，栈指针使用的是 MSP
                                        //     因为接下来要调用函数 vTaskSwitchContext, 调用函数时，返回地址自动保存到 R14 中
                                        //     所以一旦调用发生，R14 的值会被覆盖，因此需要入栈保护
                                        //     PendSV 中断服务函数执行完毕后，返回的时候需要根据 R14 的值来决定
                                        //     返回处理器模式还是任务模式，出栈时使用的是 PSP 还是 MSP
                                        //     在上面指令中 r3 保存了 pxCurrentTCB 的值，但是在运行 vTaskSwitchContext 时不确定会不会
                                        //     改变 r3 的值，而我们后面还需要使用 r3 来操作 pxCurrentTCB，所以为了保险，r3 也入栈保存一下

    // 将 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值存储到 r0，该宏在 rtosConfig.h 中定义
    // 具体描述见 rtosConfig.h
    __asm("mov r0, %0"::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY):"memory");

    __asm("msr basepri, r0");           //     关中断，进入临界段，因为接下来要更新全局指针 pxCurrentTCB 的值
    __asm("dsb");                       //     数据同步屏障，确保之前存储器访问已经完成
    __asm("isb");                       //     指令同步屏障，确保之前指令都已经执行完毕
    __asm("bl vTaskSwitchContext");     // (5) 调用 vTaskSwitchContext() 函数，选择优先级最高的任务，然后更新 pxCurrentTCB
    __asm("mov r0, #0");                //     r0 = 0
    __asm("msr basepri, r0");           //     接往 BASEPRI 写 0，退出临界段，开中断
    __asm("ldmia sp!, {r3, r14}");      //     从主堆栈中恢复寄存器 r3 和 r14 的值，恢复 (4) 处保存的值。此时的 sp 使用的是 MSP

    __asm("ldr r1, [r3]");              //     加载 r3 指向的内容到 r1，r3 存放的是 pxCurrentTCB 的地址，即让 r1 = pxCurrentTCB
                                        //     pxCurrentTCB 在 vTaskSwitchContext 中被更新，指向下一个将要运行的任务的 TCB
    __asm("ldr r0, [r1]");              //     加载 r1 指向的内容到 r0，即下一个要运行的任务的栈顶指针
    __asm("ldmia r0!, {r4-r11}");       //     以 r0 作为基地址，将下一个要运行的任务的任务栈的内容加载到 CPU 寄存器 r4~r11
    __asm("msr psp, r0");               //     更新 psp 的值，等下异常退出时，会以 psp 作为基地址，将任务栈中剩下的内容自动加载到 CPU 寄存器
    __asm("isb");                       //     指令同步屏障，确保之前指令都已经执行完毕
    __asm("bx r14");                    // (6) 中断发生时，R14 中保存异常返回标志，包括返回后进入任务模式还是处理器模式、使用 PSP 堆栈指针还是 MSP 堆栈指针
                                        //     当调用 bx r14 指令后，系统以 PSP 作为 SP 指针出栈，把接下来要运行的新任务的任务栈中剩下的内容加载到 CPU 寄存器
    __asm("nop");
}


// 进入临界段，不带中断保护版本，不能嵌套
void vPortEnterCritical(void)
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;
    if(uxCriticalNesting == 1)
    {
        // 如果 uxCriticalNesting 等于 1，即一层嵌套，要确保当前没有中断活跃
        // 即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0
        // configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0)
    }
}

// 退出临界段，不带中断保护版本，不能嵌套
void vPortExitCritical(void)
{
    // configASSERT(uxCriticalNesting);
    uxCriticalNesting--;
    if (uxCriticalNesting == 0)
    {
        portENABLE_INTERRUPTS();
    }
}

// 系统时钟中断服务程序
void xPortSysTickHandler(void)
{	
    portSET_INTERRUPT_MASK_FROM_ISR();      // 关中断
    if(xTaskIncrementTick() != pdFALSE) 
    { 
        // 触发 PendS，切换任务
        taskYIELD(); 
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR(0);   // 开中断
}


// 系统时钟初始化
void vPortSetupTimerInterrupt( void )
{
    // 设置重装载寄存器的值
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    
    /* 设置系统定时器的时钟等于内核时钟
       使能SysTick 定时器中断
       使能SysTick 定时器 */
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | 
                                  portNVIC_SYSTICK_INT_BIT |
                                  portNVIC_SYSTICK_ENABLE_BIT); 
}
